package com.hx.exception.tools;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;

public class CatchEx<T> {

    private final Class<? extends Throwable> needProcessExceptionClass;
    private List<Consumer<Throwable>> errorConsumers = new ArrayList<>();
    private RecoverCallback<T> recoverCallback;

    public CatchEx(Class<? extends Throwable> needProcessExceptionClass) {
        Objects.requireNonNull(needProcessExceptionClass, "needProcessExceptionClass must not be null");
        this.needProcessExceptionClass = needProcessExceptionClass;
    }

    public static <T> CatchEx<T> defaults() {
        return new CatchEx<>(Throwable.class);
    }

    public CatchEx<T> onError(Consumer<Throwable> errorConsumer) {
        errorConsumers.add(errorConsumer);
        return this;
    }

    public CatchEx<T> recover(RecoverCallback<T> recoverCallback) {
        this.recoverCallback = recoverCallback;
        return this;
    }

    public final <E extends Throwable> T execute(ReturnFunc<T, E> func) throws E {
        return doExecute(func);
    }


    public final <E extends Throwable> void executeVoid(VoidFunc func) throws E {
        try {
            func.process();
        } catch (Throwable throwable) {
            handleException(throwable);
            if (supportDealException(throwable)) {
                throw throwable;
            }
            doRecover(throwable);
        }
    }

    private <E extends Throwable> T doExecute(ReturnFunc<T, E> func) throws E {
        T result = null;
        try {
            result = func.process();
        } catch (Throwable throwable) {
            handleException(throwable);
            if (supportDealException(throwable)) {
                throw throwable;
            }
            result = doRecover(throwable);
        }
        return result;
    }

    private void handleException(Throwable ex) {
        errorConsumers.forEach(consumer -> consumer.accept(ex));
    }

    private <E extends Throwable> boolean supportDealException(Throwable ex) {
        return !needProcessExceptionClass.isAssignableFrom(ex.getClass());
    }

    private T doRecover(Throwable throwable) {
        if (Objects.isNull(recoverCallback)) {
            return null;
        }
        return recoverCallback.recover(throwable);
    }
}
